﻿#region File and License Information
/*
<File>
	<License>
		Copyright © 2009 - 2017, Daniel Vaughan. All rights reserved.
		This file is part of Calcium (http://calciumsdk.net), 
		which is released under the MIT License.
		See file /Documentation/License.txt for details.
	</License>
	<CreationDate>2008-11-03 23:32:33Z</CreationDate>
</File>
*/
#endregion

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

using Outcoder.Concurrency;

#if WINDOWS_UWP || NETFX_CORE
using Windows.UI.Core;
using Windows.ApplicationModel.Core;
using Windows.UI.Xaml;
#else
using System.Windows.Threading;
#endif

namespace Outcoder
{
	/// <summary>
	/// Singleton class providing the default implementation 
	/// for the <see cref="ISynchronizationContext"/>, specifically for the UI thread.
	/// </summary>
	public partial class UISynchronizationContext : ISynchronizationContext
	{
#if WINDOWS_UWP || NETFX_CORE
		CoreDispatcher dispatcher;
#else
		DispatcherSynchronizationContext context;
		Dispatcher dispatcher;
#endif

		#region Singleton implementation

		static readonly UISynchronizationContext instance = new UISynchronizationContext();
		
		/// <summary>
		/// Gets the singleton instance.
		/// </summary>
		/// <value>The singleton instance.</value>
		public static UISynchronizationContext Instance
		{
			get
			{
				return instance;
			}
		}

		UISynchronizationContext()
		{
			/* Intentionally left blank. */
		}

#endregion

		public void Initialize()
		{
			EnsureInitialized();
		}

		readonly object initializationLock = new object();

#if WINDOWS_UWP || NETFX_CORE

		bool initialized;

		void EnsureInitialized()
		{
			if (initialized)
			{
				return;
			}

			lock (initializationLock)
			{
				if (initialized)
				{
					return;
				}

				try
				{
					dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
				}
				catch (InvalidOperationException ex)
				{
					/* TODO: Make localizable resource. */
					throw new ConcurrencyException("Initialize called from non-UI thread.", ex);
				}

				initialized = dispatcher != null;
			}
		}
#else
		void EnsureInitialized()
		{
			if (dispatcher != null && context != null)
			{
				return;
			}

			lock (initializationLock)
			{
				if (dispatcher != null && context != null)
				{
					return;
				}

				try
				{
#if SILVERLIGHT
					dispatcher = System.Windows.Deployment.Current.Dispatcher;
#else
					dispatcher = Dispatcher.CurrentDispatcher;
#endif
					context = new DispatcherSynchronizationContext(dispatcher);
				}
				catch (InvalidOperationException)
				{
					/* TODO: Make localizable resource. */
					throw new ConcurrencyException("Initialised called from non-UI thread."); 
				}
			}
		}

		[EditorBrowsable(EditorBrowsableState.Never)]
		public void Initialize(Dispatcher dispatcher)
		{
			ArgumentValidator.AssertNotNull(dispatcher, "dispatcher");
			lock (initializationLock)
			{
				this.dispatcher = dispatcher;
				context = new DispatcherSynchronizationContext(dispatcher);
			}
		}

#endif

		InvokedExceptionHandler invokedExceptionHandler;

		public InvokedExceptionHandler InvokedExceptionHandler
		{
			get
			{
				return invokedExceptionHandler;
			}
			set
			{
				invokedExceptionHandler = value;
			}
		}

		SendOrPostCallback GetWrappedCallback(SendOrPostCallback callback, object state,
			string memberName = null,
			string filePath = null,
			int lineNumber = 0)
		{
			if (invokedExceptionHandler == null)
			{
				return callback;
			}

			SendOrPostCallback wrappedCallback = delegate
			{
				try
				{
					callback.Invoke(state);
				}
				catch (Exception ex)
				{
					var args = new InvokedExceptionArgs(memberName, filePath, lineNumber, ex);
					bool handled = invokedExceptionHandler(this, args);

					if (!handled)
					{
						throw;
					}
				}
			};

			return wrappedCallback;
		}

		Action GetWrappedAction(Action action,
			string memberName = null,
			string filePath = null,
			int lineNumber = 0)
		{
			if (invokedExceptionHandler == null)
			{
				return action;
			}

			Action invokableAction = delegate
			{
				try
				{
					action();
				}
				catch (Exception ex)
				{
					var args = new InvokedExceptionArgs(memberName, filePath, lineNumber, ex);
					bool handled = invokedExceptionHandler(this, args);

					if (!handled)
					{
						throw;
					}
				}
			};

			return invokableAction;
		}

		Func<Task> GetWrappedAction(Func<Task> action,
			string memberName = null,
			string filePath = null,
			int lineNumber = 0)
		{
			if (invokedExceptionHandler == null)
			{
				return action;
			}

			Func<Task> invokableAction = async () =>
			{
				try
				{
					await action();
				}
				catch (Exception ex)
				{
					var args = new InvokedExceptionArgs(memberName, filePath, lineNumber, ex);
					bool handled = invokedExceptionHandler(this, args);

					if (!handled)
					{
						throw;
					}
				}
			};

			return invokableAction;
		}
		
		public void InvokeWithoutBlocking(SendOrPostCallback callback, object state, 
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(callback, "callback");
			EnsureInitialized();

			var invokableCallback = GetWrappedCallback(callback, state, memberName, filePath, lineNumber);

#if WINDOWS_UWP || NETFX_CORE
#pragma warning disable 4014
			dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => invokableCallback(state));
#pragma warning restore 4014
#else
			context.Post(invokableCallback, state);
#endif
		}

		public void InvokeWithoutBlocking(Action action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(action, "action");
			EnsureInitialized();

			Action invokableAction = GetWrappedAction(action, memberName, filePath, lineNumber);

#if WINDOWS_UWP || NETFX_CORE
#pragma warning disable 4014
			dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => invokableAction());
#pragma warning restore 4014
#else
			dispatcher.BeginInvoke(invokableAction);
#endif
		}

		/* TODO: return a task and rename to *Async */
		public async void InvokeAndBlockUntilCompletion(SendOrPostCallback callback, object state,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(callback, "callback");
			EnsureInitialized();

			var invokableCallback = GetWrappedCallback(callback, state, memberName, filePath, lineNumber);

#if WINDOWS_UWP || NETFX_CORE
			await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => callback(state));
#else
			context.Send(invokableCallback, state);
#endif
		}

		/* TODO: return a task and rename to *Async */
		public async void InvokeAndBlockUntilCompletion(Action action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(action, "action");
			EnsureInitialized();

			Action invokableAction = GetWrappedAction(action, memberName, filePath, lineNumber);

#if WINDOWS_UWP || NETFX_CORE
			if (dispatcher.HasThreadAccess)
			{
				invokableAction();
			}
			else
			{
				await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => invokableAction());
			}
#else
			if (dispatcher.CheckAccess())
			{
				invokableAction();
			}
			else
			{
				context.Send(delegate { invokableAction(); }, null);
			}
#endif
		}

		public void InvokeIfRequired(Action action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			Action invokableAction = GetWrappedAction(action, memberName, filePath, lineNumber);

			if (InvokeRequired)
			{
#if WINDOWS_UWP || NETFX_CORE
#pragma warning disable 4014
				dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => invokableAction());
#pragma warning restore 4014
#else
				dispatcher.BeginInvoke(invokableAction);
#endif
			}
			else
			{
				invokableAction();
			}
		}

		public async Task InvokeIfRequiredAsync(
			Func<Task> action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(action, "action");

			Func<Task> invokableAction = GetWrappedAction(action, memberName, filePath, lineNumber);

			if (InvokeRequired)
			{
				InvokeAndBlockUntilCompletion(async arg => await invokableAction(), null);
			}
			else
			{
				await invokableAction();
			}
		}

		public bool InvokeRequired
		{
			get
			{
				EnsureInitialized();
#if WINDOWS_UWP || NETFX_CORE
				return !dispatcher.HasThreadAccess;
#else
				return !dispatcher.CheckAccess();
#endif
			}
		}
	}
}
